package zero.mybatisplus.dynamictable.interceptor;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import zero.mybatisplus.dynamictable.annotation.DynamicTable;
import zero.mybatisplus.dynamictable.emnus.TypeEnums;
import zero.mybatisplus.dynamictable.param.DynamicTableParam;
import zero.mybatisplus.dynamictable.threadlocal.ThreadLocalDynamicTable;
import zero.mybatisplus.dynamictable.util.DynamicTableUtil;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author zero
 */
@Component
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public class DynamicTableInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 方法一
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        //先拦截到RoutingStatementHandler，里面有个StatementHandler类型的delegate变量，其实现类是BaseStatementHandler，然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        //id为执行的mapper方法的全路径名，如com.uv.dao.UserMapper.insertUser
        Class<?> entityClass = getEntityClass(mappedStatement.getId());

        Map<String, String> map = new HashMap<>();
        //处理Mapper对应实体类
        handleEntityClass(entityClass, map);
        //获取sql对象
        BoundSql boundSql = statementHandler.getBoundSql();

        //处理参数对象
        Object parameterObject = boundSql.getParameterObject();
        handleParam(parameterObject, map);
        if (CollectionUtils.isEmpty(map)) {
            return invocation.proceed();
        }
        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, replaceSql(boundSql.getSql(), map));
        return invocation.proceed();
    }


    private static String replaceSql(String sql, Map<String, String> map) {
        String handleSql = sql;
        for (Map.Entry<String, String> table : map.entrySet()) {
            String key = table.getKey();
            String value = table.getValue();
            handleSql = handleSql.replaceAll("(?<![A-Za-z0-9_.])" + key + "(?![A-Za-z0-9_])", value);
        }
        return handleSql;
    }

    /**
     * 获取实体类Class
     *
     * @param id
     * @return
     * @throws ClassNotFoundException
     */
    private static Class<?> getEntityClass(String id) throws ClassNotFoundException {
        Class<?> aClass = Class.forName(id.substring(0, id.lastIndexOf(".")));
        Type[] genericInterfaces = aClass.getGenericInterfaces();
        for (Type type : genericInterfaces) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                return (Class<?>) pt.getActualTypeArguments()[0];
            }
        }
        return null;
    }

    /**
     * 处理实体类Class
     *
     * @param entityClass
     * @param map
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private static void handleEntityClass(Class<?> entityClass, Map<String, String> map) throws InstantiationException, IllegalAccessException {
        DynamicTableParam dynamicTableParam = ThreadLocalDynamicTable.getDynamicTableParam();
        TableName tableName = entityClass.getAnnotation(TableName.class);
        if (!Objects.isNull(dynamicTableParam) && !Objects.isNull(tableName)) {
            map.put(tableName.value(), DynamicTableUtil.getDynamicTableName(dynamicTableParam));
        }
        handleEntityClass(entityClass, map, null);
    }

    /**
     * 处理实体类Class
     *
     * @param entityClass
     * @param map
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private static void handleEntityClass(Class<?> entityClass, Map<String, String> map, Object obj) throws InstantiationException, IllegalAccessException {
        if (Objects.nonNull(entityClass) && entityClass.isAnnotationPresent(DynamicTable.class)) {
            DynamicTable dynamicTable = entityClass.getAnnotation(DynamicTable.class);
            TableName tableName = entityClass.getAnnotation(TableName.class);
            String databaseName = dynamicTable.databaseName();
            TypeEnums type = dynamicTable.type();
            String dynamicTableField = "";
            switch (type) {
                case YEAR_TABLE:
                case YEAR_MONTH_TABLE:
                    dynamicTableField = dynamicTable.dateField();
                    break;
            }
            String tableNameValue = tableName.value();
            String targetName = handleDatabaseAndTable(databaseName, handleField(entityClass, type, tableNameValue, dynamicTableField, obj));
            if (!Objects.equals(targetName, tableNameValue)) {
                map.put(tableNameValue, targetName);
            }
        }
    }

    /**
     * 处理数据库和表
     *
     * @param databaseName
     * @param tableName
     * @return
     */
    private static String handleDatabaseAndTable(String databaseName, String tableName) {
        return StringUtils.isBlank(databaseName) ? tableName : databaseName + "." + tableName;
    }

    /**
     * 处理字段信息
     *
     * @param entityClass
     * @param type
     * @param tableName
     * @param dynamicTableField
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private static String handleField(Class<?> entityClass, TypeEnums type, String tableName, String dynamicTableField, Object obj) throws IllegalAccessException, InstantiationException {
        if (StringUtils.isBlank(dynamicTableField)) {
            return tableName;
        }
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            TableField tableField = field.getAnnotation(TableField.class);
            String fieldName = null;
            if (!Objects.isNull(tableField)) {
                fieldName = tableField.value();
            }
            if (StringUtils.isBlank(fieldName)) {
                fieldName = field.getName();
            }
            if (Objects.equals(fieldName, dynamicTableField)) {
                field.setAccessible(true);
                Object o;
                if (Objects.isNull(obj)) {
                    o = field.get(entityClass.newInstance());
                } else {
                    o = ReflectionUtils.getField(field, obj);
                }
                return DynamicTableUtil.getDynamicTableName(DynamicTableParam.initDateValue(tableName, type, o));
            }
        }
        return tableName;
    }

    public static void handleParam(Object parameterObject, Map<String, String> map) throws IllegalAccessException, InstantiationException {
        if (Objects.isNull(parameterObject)) {
            return;
        }
        if (parameterObject instanceof Map) {
            Map<String, Object> parameterMap = (Map) parameterObject;
            for (Map.Entry<String, Object> maps : parameterMap.entrySet()) {
                Object value = maps.getValue();
                if (Objects.isNull(value)) {
                    continue;
                }

                if (value instanceof DynamicTableParam) {
                    DynamicTableParam dynamicTableParam = (DynamicTableParam) value;
                    map.put(dynamicTableParam.getTableName(), handleDatabaseAndTable(dynamicTableParam.getDatabaseName(),
                            DynamicTableUtil.getDynamicTableName(dynamicTableParam)));
                }
            }
        } else if (parameterObject instanceof DynamicTableParam) {
            DynamicTableParam dynamicTableParam = (DynamicTableParam) parameterObject;
            map.put(dynamicTableParam.getTableName(), handleDatabaseAndTable(dynamicTableParam.getDatabaseName(),
                    DynamicTableUtil.getDynamicTableName(dynamicTableParam)));
        } else {
            handleEntityClass(parameterObject.getClass(), map, parameterObject);
        }
    }
}
